/*
 * Copyright Radosław Smogura, 2012. All rights reserved.
 *
 * The content of this file is confidential and it is intelectual property
 * of copyright owners.
 * 
 *
 * $URL: http://reprap-ide.googlecode.com/svn/trunk/RepRapIDE/RepRapProject/src/main/java/eu/softper/reprap/generator/MultiThreadSlicerImpl.java $
 * $Id: MultiThreadSlicerImpl.java 3 2012-11-19 10:58:30Z mail@smogura.eu $
 * $Revision: 3 $
 */
package eu.softper.reprap.generator;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;

/** This is like {@link SlicerImpl}, but allows multi-thread processing of
 * layers.
 *
 * @author Radosław Smogura
 */
public class MultiThreadSlicerImpl<T extends Number & Comparable<T>> {
    private final int numberOfThreads;
    private final BigDecimal start;
    private final BigDecimal stepSize;
    private final List<SceneTriangle<T>> orderedTriangles;
    private SlicerCallable[] slicerCallables;
    private Future<Void>[] slicerTasks;
    private GenerationContextImpl ctx;
    /** XXX Creating executor each times isn't good, allow to get executor
     * as parameter
     */
    private ThreadPoolExecutor executor;
    
    /** Protected by synchronized. */
    private List<Layer> composedLayers;
    
    /** If tasks has been started. Protected by synchronized. */
    private boolean started;
    
    private NumericFactory<T> numericFactory;
    
    protected void createRunnables() {
        /** Compiler shown generic array creation. */
        slicerCallables = (SlicerCallable[]) Array.newInstance(SlicerCallable.class, numberOfThreads);
        BigDecimal runnableStartPos = start;
        BigDecimal stepPerRunnable = stepSize.multiply(BigDecimal.valueOf(numberOfThreads));
        
        for (int i=0; i < numberOfThreads; i++) {
            SlicerImpl slicer = new SlicerImpl(numericFactory, ctx);
            slicer.initialize(i, numberOfThreads, runnableStartPos, stepPerRunnable, orderedTriangles);
            slicerCallables[i] = new SlicerCallable(slicer);
            
            /* Prepare next step. */
            runnableStartPos = runnableStartPos.add(stepSize);
        }
    }
    
    public static <Y extends Number & Comparable<Y>> MultiThreadSlicerImpl<Y> createInstance(NumericFactory<Y> numericFactory, int numberOfThreads, BigDecimal start, BigDecimal stepSize, List<SceneTriangle<Y>> orderedTriangles, GenerationContextImpl ctx) {
        return new MultiThreadSlicerImpl(numericFactory, numberOfThreads, start, stepSize, orderedTriangles, ctx);
    }
    
    public MultiThreadSlicerImpl(NumericFactory<T> numericFactory, int numberOfThreads, BigDecimal start, BigDecimal stepSize, List<SceneTriangle<T>> orderedTriangles, GenerationContextImpl ctx) {
        this.numberOfThreads = numberOfThreads;
        this.start = start;
        this.stepSize = stepSize;
        this.orderedTriangles = orderedTriangles;
        this.ctx = ctx;
        this.numericFactory = numericFactory;
                
        if (numberOfThreads < 1) {
            throw new IllegalArgumentException("Number of threads must be at lest 1.");
        }

        createRunnables();
    }
    
    public synchronized void run() {
        executor = new ThreadPoolExecutor(numberOfThreads, numberOfThreads, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(numberOfThreads));
        slicerTasks = new Future[numberOfThreads];
        for (int i=0; i < numberOfThreads; i++) {
            slicerTasks[i] = executor.submit(slicerCallables[i]);
        }
        
        started = true;
    }
    
    public synchronized List<Layer> getOutlines() {
        if (!started)
            run();
        
        if (composedLayers != null)
            return composedLayers;
        for (int i=0; i < slicerCallables.length; i++) {
            /* Retrive all results, actually ensure till all threads ends. */
            try {
                slicerTasks[i].get();
            }catch(InterruptedException ie) {
                throw new RuntimeException(ie);
            }catch(ExecutionException ee) {
                throw new RuntimeException(ee);
            }
        }
        
        /* Compose outlines. */
        ArrayList<Layer> layersLocal = new ArrayList<Layer>();
        for (int i=0; i < slicerCallables.length; i++) {
            layersLocal.addAll(slicerCallables[i].layers);
        }
        
        /* Actually sort should not be needed, just add one bye one from
         * each collection. It's topic to think...
         */
        Collections.sort(layersLocal, Layer.ZPositionComparator.INSTANCE);
        
        /* Trim empty layers - we use array list so it will be fast. */
        while (layersLocal.size() > 0) {
            Layer l = layersLocal.get(layersLocal.size() - 1);
            if (l.getOutlines().isEmpty()) {
                layersLocal.remove(layersLocal.size() - 1);
            }else {
                break;
            }
        }
        
        this.composedLayers = layersLocal;
        
        return composedLayers;
    }
    
    protected class SlicerCallable implements Callable<Void> {
        private LinkedList<Layer> layers;
        private SlicerImpl<T> slicer;

        public SlicerCallable(SlicerImpl<T> slicerImpl) {
            this.slicer = slicerImpl;
        }

        @Override
        public Void call() {
            layers = new LinkedList<>();
            
            while (slicer.processLayer()) {
                layers.add(slicer.getLayer());
            }
            
            return null;
        }
    }
}
